// FSFolderCopy.cpp

#include "StdAfx.h"

#include <Winbase.h>

#include "Common/String/StringConvert.h"

#include "Common/System/DLL.h"
#include "Common/System/Error.h"
#include "FileDir.h"

#include "FilePathAutoRename.h"

#include "FSFolder.h"

#include "Common/Defs.h"

using namespace Common;
using namespace System;
using namespace FileSystem;

namespace Common {
namespace FileSystem {

namespace Folder {

struct CProgressInfo
{
  UINT64 StartPos;
  IProgress *Progress;
};

#ifndef PROGRESS_CONTINUE

#define PROGRESS_CONTINUE 0
#define PROGRESS_CANCEL 1

#define COPY_FILE_FAIL_IF_EXISTS 0x00000001

typedef
DWORD
(WINAPI* LPPROGRESS_ROUTINE)(
    LARGE_INTEGER TotalFileSize,
    LARGE_INTEGER TotalBytesTransferred,
    LARGE_INTEGER StreamSize,
    LARGE_INTEGER StreamBytesTransferred,
    DWORD dwStreamNumber,
    DWORD dwCallbackReason,
    HANDLE hSourceFile,
    HANDLE hDestinationFile,
    LPVOID lpData
    );

#endif

static DWORD CALLBACK CopyProgressRoutine(
  LARGE_INTEGER /* TotalFileSize */,          // file size
  LARGE_INTEGER TotalBytesTransferred,  // bytes transferred
  LARGE_INTEGER /* StreamSize */,             // bytes in stream
  LARGE_INTEGER /* StreamBytesTransferred */, // bytes transferred for stream
  DWORD /* dwStreamNumber */,                 // current stream
  DWORD /* dwCallbackReason */,               // callback reason
  HANDLE /* hSourceFile */,                   // handle to source file
  HANDLE /* hDestinationFile */,              // handle to destination file
  LPVOID lpData                         // from CopyFileEx
)
{
  CProgressInfo &progressInfo = *(CProgressInfo *)lpData;
  UINT64 completed = progressInfo.StartPos + TotalBytesTransferred.QuadPart;
  if (progressInfo.Progress->SetCompleted(&completed) != S_OK)
    return PROGRESS_CANCEL;
  return PROGRESS_CONTINUE;
}

typedef BOOL (WINAPI * CopyFileExPointer)(
    IN LPCSTR lpExistingFileName,
    IN LPCSTR lpNewFileName,
    IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
    IN LPVOID lpData OPTIONAL,
    IN LPBOOL pbCancel OPTIONAL,
    IN DWORD dwCopyFlags
    );

typedef BOOL (WINAPI * CopyFileExPointerW)(
    IN LPCWSTR lpExistingFileName,
    IN LPCWSTR lpNewFileName,
    IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
    IN LPVOID lpData OPTIONAL,
    IN LPBOOL pbCancel OPTIONAL,
    IN DWORD dwCopyFlags
    );


static bool MyCopyFile(LPCWSTR existingFile, LPCWSTR newFile, IProgress *progress, UINT64 &completedSize)
{
  CProgressInfo progressInfo;
  progressInfo.Progress = progress;
  progressInfo.StartPos = completedSize;
  BOOL CancelFlag = FALSE;	
  if (::CopyFileEx(existingFile, newFile, CopyProgressRoutine, &progressInfo, &CancelFlag, COPY_FILE_FAIL_IF_EXISTS))
      return true;
    #ifdef WIN_LONG_PATH
    CString longPathExisting, longPathNew;
    if (!FileSystem::GetLongPaths(existingFile, newFile, longPathExisting, longPathNew))
      return false;
    if (::CopyFileEx(longPathExisting, longPathNew, CopyProgressRoutine,  &progressInfo, &CancelFlag, COPY_FILE_FAIL_IF_EXISTS))
      return true;
    #endif
    return false;
  
}

typedef BOOL (WINAPI * MoveFileWithProgressPointer)(
    IN LPCWSTR lpExistingFileName,
    IN LPCWSTR lpNewFileName,
    IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
    IN LPVOID lpData OPTIONAL,
    IN DWORD dwFlags
    );

static bool MoveFile(LPCWSTR existingFile, LPCWSTR newFile, IProgress *progress, UINT64 &completedSize)
{
    CProgressInfo progressInfo;
    progressInfo.Progress = progress;
    progressInfo.StartPos = completedSize;

      if (::MoveFileWithProgress(existingFile, newFile, CopyProgressRoutine, 
				&progressInfo, MOVEFILE_COPY_ALLOWED))
        return true;
      if (::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
      {
        #ifdef WIN_LONG_PATH
        CString longPathExisting, longPathNew;
        if (!FileSystem::GetLongPaths(existingFile, newFile, longPathExisting, longPathNew))
          return false;
        if (::MoveFileWithProgress(longPathExisting, longPathNew, CopyProgressRoutine,
            &progressInfo, MOVEFILE_COPY_ALLOWED))
          return true;
        #endif
        if (::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
          return false;
      }
    return FileSystem::MoveFile(existingFile, newFile);
}

static HRESULT CopyFile(
    const CString &srcPath,
	const File::CFileInfo &srcFileInfo,
    const CString &destPathSpec,
    IFolderOperationsExtractCallback *callback,
    UINT64 &completedSize)
{
  CString destPath = destPathSpec;
  HRESULT hRes(S_OK);
  if (destPath.CompareNoCase(srcPath) == 0)
  {
    CString message = CString(_T("can not move file \'")) + destPath + CString(_T("\' onto itself"));
    hRes  = callback->ShowMessage(message);
	if(S_OK != hRes)
		return hRes;
    return E_ABORT;
  }

  INT32 writeAskResult;
  CComBSTR destPathResult;
  hRes = callback->AskWrite(
      srcPath,
      BoolToInt(false),
	  &srcFileInfo.ModificationTime, &srcFileInfo.Size,
      destPath,
      &destPathResult,
      &writeAskResult);
  if(S_OK != hRes)
	return hRes;
  if (IntToBool(writeAskResult))
  {
    CString destPathNew = CString(destPathResult);
    hRes = callback->SetCurrentFilePath(srcPath);
	if(S_OK != hRes)
		return hRes;
    if (!MyCopyFile(srcPath, destPathNew, callback, completedSize))
    {
      CString message = Error::FormatMessage(GetLastError()) +
        CString(_T(" \'")) +
        CString(destPathNew) +
        CString(_T("\'"));
      hRes = callback->ShowMessage(message);
	  if(S_OK != hRes)
		return hRes;
      return E_ABORT;
    }
  }
  completedSize += srcFileInfo.Size;
  return callback->SetCompleted(&completedSize);
}

static CString CombinePath(const CString &folderPath, const CString &fileName)
{
  return folderPath + CString(CHAR_PATH_SEPARATOR) + fileName;
}

static HRESULT CopyFolder(
    const CString &srcPath,
    const CString &destPathSpec,
    IFolderOperationsExtractCallback *callback,
    UINT64 &completedSize)
{
  HRESULT hRes = callback->SetCompleted(&completedSize);
  if(S_OK != hRes)
	return hRes;

  const CString destPath = destPathSpec;
  int len = srcPath.GetLength();
  if (destPath.GetLength() >= len && srcPath.CompareNoCase(destPath.Left(len)) == 0)
  {
    if (destPath.GetLength() == len || destPath[len] == CHAR_PATH_SEPARATOR)
    {
      CString message = CString(_T("can not copy folder \'")) +
          destPath + CString(_T("\' onto itself"));
      hRes = callback->ShowMessage(message);
      return E_ABORT;
    }
  }

  if (!FileSystem::CreateComplexDirectory(destPath))
  {
    CString message = CString(_T("can not create folder ")) + destPath;
    hRes = callback->ShowMessage(message);
    return E_ABORT;
  }
  Find::CEnumerator enumerator(CombinePath(srcPath, _T("*")));
  Folder::CFileInfoEx fi;
  while (enumerator.Next(&fi))
  {
    const CString srcPath2 = CombinePath(srcPath, fi.Name);
    const CString destPath2 = CombinePath(destPath, fi.Name);
    if (fi.IsDir())
    {
      hRes = Folder::CopyFolder(srcPath2, destPath2, callback, completedSize);	  
    }
    else
    {
	  hRes = Folder::CopyFile(srcPath2, fi, destPath2, callback, completedSize);
    }
	if(S_OK != hRes)
		return hRes;
  }
  return S_OK;
}

STDMETHODIMP CFSFolder::CopyTo(const UINT32 *indices, UINT32 numItems,
    const wchar_t *path, IFolderOperationsExtractCallback *callback)
{
  if (numItems == 0)
    return S_OK;
  
  UINT64 numFolders, numFiles, totalSize;
  GetItemsFullSize(indices, numItems, numFolders, numFiles, totalSize, callback);
  HRESULT hRes = callback->SetTotal(totalSize);
  if(S_OK != hRes)
	return hRes;
  hRes = callback->SetNumFiles(numFiles);
  if(S_OK != hRes)
	return hRes;
  
  CString destPath = path;
  if (destPath.IsEmpty())
    return E_INVALIDARG;
  bool directName = (destPath[destPath.GetLength() - 1] != CHAR_PATH_SEPARATOR);
  if (directName)
  {
    if (numItems > 1)
      return E_INVALIDARG;
  }
    /*
    // doesn't work in network
  else
    if (!FileSystem::CreateComplexDirectory(destPath)))
    {
      DWORD lastError = ::GetLastError();
      CString message = CString(_T(can not create folder ")) +
        destPath;
      RINOK(callback->ShowMessage(message));
      return E_ABORT;
    }
    */

  UINT64 completedSize = 0;
  hRes = callback->SetCompleted(&completedSize);
  if(S_OK != hRes)
	return hRes;
  for (UINT32 i = 0; i < numItems; i++)
  {
    const CDirItem &fi = *_refs[indices[i]];
    CString destPath2 = destPath;
    if (!directName)
      destPath2 += fi.Name;
    CString srcPath = _path + GetPrefix(fi) + fi.Name;
    if (fi.IsDir())
    {
      hRes = Folder::CopyFolder(srcPath, destPath2, callback, completedSize);
    }
    else
    {
      hRes = Folder::CopyFile(srcPath, fi, destPath2, callback, completedSize);
    }
	if(S_OK != hRes)
		return hRes;
  }
  return S_OK;
}

/////////////////////////////////////////////////
// Move Operations

HRESULT MoveFile(
    const CString &srcPath,
    const File::CFileInfo &srcFileInfo,
    const CString &destPathSpec,
    IFolderOperationsExtractCallback *callback,
    UINT64 &completedSize)
{
  CString destPath = destPathSpec;
  HRESULT hRes(S_OK);
  if (destPath.CompareNoCase(srcPath) == 0)
  {
    CString message = CString(_T("can not move file \'"))
         + destPath +
        CString(_T("\' onto itself"));
        hRes = callback->ShowMessage(message);
    return E_ABORT;
  }

  INT32 writeAskResult;
  CComBSTR destPathResult;
  hRes = callback->AskWrite(
      srcPath,
      BoolToInt(false),
	  &srcFileInfo.ModificationTime, &srcFileInfo.Size,
      destPath,
      &destPathResult,
      &writeAskResult);
  if(S_OK != hRes)
	return hRes;
  if (IntToBool(writeAskResult))
  {
    CString destPathNew = CString(destPathResult);
    hRes = callback->SetCurrentFilePath(srcPath);
	if(S_OK != hRes)
		return hRes;
    if (!Folder::MoveFile(srcPath, destPathNew, callback, completedSize))
    {
      CString message = CString(_T("can not move to file ")) + destPathNew;
      hRes = callback->ShowMessage(message);	  
    }
  }
  completedSize += srcFileInfo.Size;
  hRes = callback->SetCompleted(&completedSize);
  return hRes;
}

HRESULT MoveFolder(
    const CString &srcPath,
    const CString &destPathSpec,
    IFolderOperationsExtractCallback *callback,
    UINT64 &completedSize)
{
  CString destPath = destPathSpec;
  int len = srcPath.GetLength();
  HRESULT hRes(S_OK);
  if (destPath.GetLength() >= len && srcPath.CompareNoCase(destPath.Left(len)) == 0)
  {
    if (destPath.GetLength() == len || destPath[len] == CHAR_PATH_SEPARATOR)
    {
      CString message = CString(_T("can not move folder \'")) +
          destPath + CString(_T("\' onto itself"));
      hRes = callback->ShowMessage(message);
      return E_ABORT;
    }
  }

  if (Folder::MoveFile(srcPath, destPath, callback, completedSize))
    return S_OK;

  if (!FileSystem::CreateComplexDirectory(destPath))
  {
    CString message = CString(_T("can not create folder ")) +  destPath;
    hRes = callback->ShowMessage(message);
    return E_ABORT;
  }
  {
	Find::CEnumerator enumerator(CombinePath(srcPath, _T("*")));
    CFileInfoEx fi;
    while (enumerator.Next(&fi))
    {
      const CString srcPath2 = CombinePath(srcPath, fi.Name);
      const CString destPath2 = CombinePath(destPath, fi.Name);
      if (fi.IsDir())
      {
		  hRes = Folder::MoveFolder(srcPath2, destPath2, callback, completedSize);
      }
      else
      {
		  hRes = Folder::MoveFile(srcPath2, fi, destPath2, callback, completedSize);
      }
	  if(S_OK != hRes)
		return hRes;
    }
  }
  if (!FileSystem::RemoveDirectory(srcPath))
  {
    CString message = CString(_T("can not remove folder")) + srcPath;
    hRes = callback->ShowMessage(message);
    return E_ABORT;
  }
  return S_OK;
}

STDMETHODIMP CFSFolder::MoveTo(
    const UINT32 *indices,
    UINT32 numItems,
    const wchar_t *path,
    IFolderOperationsExtractCallback *callback)
{
  if (numItems == 0)
    return S_OK;

  UINT64 numFolders, numFiles, totalSize;
  GetItemsFullSize(indices, numItems, numFolders, numFiles, totalSize, callback);
  HRESULT hRes = callback->SetTotal(totalSize);
  if(S_OK != hRes)
	return hRes;
  hRes = callback->SetNumFiles(numFiles);
  if(S_OK != hRes)
	return hRes;

  CString destPath = path;
  if (destPath.IsEmpty())
    return E_INVALIDARG;
  bool directName = (destPath[destPath.GetLength() - 1] != CHAR_PATH_SEPARATOR);
  if (directName)
  {
    if (numItems > 1)
      return E_INVALIDARG;
  }
  else
    if (!FileSystem::CreateComplexDirectory(destPath))
    {
      CString message = CString(_T("can not create folder ")) +
        destPath;
      hRes  = callback->ShowMessage(message);
      return E_ABORT;
    }

  UINT64 completedSize = 0;
  hRes = callback->SetCompleted(&completedSize);
  if(S_OK != hRes)
	return hRes;
  for (UINT32 i = 0; i < numItems; i++)
  {
    const CDirItem &fi = *_refs[indices[i]];
    CString destPath2 = destPath;
    if (!directName)
      destPath2 += fi.Name;
    CString srcPath = _path + GetPrefix(fi) + fi.Name;
    if (fi.IsDir())
    {
      hRes = Folder::MoveFolder(srcPath, destPath2, callback, completedSize);
    }
    else
    {
      hRes = Folder::MoveFile(srcPath, fi, destPath2, callback, completedSize);
    }
	if(S_OK != hRes)
		return hRes;
  }
  return S_OK;
}

STDMETHODIMP CFSFolder::CopyFrom(const wchar_t * /* fromFolderPath */,
    const wchar_t ** /* itemsPaths */, UINT32 /* numItems */, IProgress * /* progress */)
{
  /*
  UINT64 numFolders, numFiles, totalSize;
  numFiles = numFolders = totalSize = 0;
  UINT32 i;
  for (i = 0; i < numItems; i++)
  {
    CString path = (CString)fromFolderPath + itemsPaths[i];

    CFileInfoW fi;
    if (!FindFile(path, fi))
      return ::GetLastError();
    if (fi.IsDir())
    {
      UINT64 subFolders, subFiles, subSize;
      RINOK(GetFolderSize(CombinePath(path, fi.Name), subFolders, subFiles, subSize, progress));
      numFolders += subFolders;
      numFolders++;
      numFiles += subFiles;
      totalSize += subSize;
    }
    else
    {
      numFiles++;
      totalSize += fi.Size;
    }
  }
  RINOK(progress->SetTotal(totalSize));
  RINOK(callback->SetNumFiles(numFiles));
  for (i = 0; i < numItems; i++)
  {
    CString path = (CString)fromFolderPath + itemsPaths[i];
  }
  return S_OK;
  */
  return E_NOTIMPL;
}

}

}}